﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;

namespace CommonHelper
{
    public static class SerialComHelper
    {
        public const uint DIGCF_ALLCLASSES = 0x00000004;

        public const uint DIGCF_DEFAULT = 0x00000001;

        public const uint DIGCF_DEVICEINTERFACE = 0x00000010;

        // only valid with DIGCF_DEVICEINTERFACE
        public const uint DIGCF_PRESENT = 0x00000002;

        public const uint DIGCF_PROFILE = 0x00000008;

        private const int DICS_FLAG_GLOBAL = 0x00000001;

        private const short INVALID_HANDLE_VALUE = -1;

        private const int MAX_DEV_LEN = 256;

        private const int SPDRP_DEVICEDESC = (0x00000000);

        //返回值最大长度
        private const int SPDRP_FRIENDLYNAME = (0x0000000C);

        // FriendlyName (R/W)
        // DeviceDesc (R/W)
        private const int SPDRP_HARDWAREID = (0x00000001);

        public static string GetStringValue(IntPtr hkey, string valName)
        {
            int type = 0;
            int datasize = 0;
            int ret = RegQueryValueEx(hkey, valName, null, ref type, null, ref datasize);
            if (ret == 0)
                if (type == 1)
                {
                    byte[] blob = new byte[datasize];
                    ret = RegQueryValueEx(hkey, valName, null, ref type, blob, ref datasize);
                    UnicodeEncoding unicode = new UnicodeEncoding();
                    return unicode.GetString(blob);
                }
            return null;
        }

        public static List<ComInfo> ListPorts()
        {
            Guid GUID_DEVCLASS_PORTS = new Guid("4d36e978-e325-11ce-bfc1-08002be10318");
            Guid GUID_DEVCLASS_MODEM = new Guid("4d36e96d-e325-11ce-bfc1-08002be10318");
            Guid GUID_DEVINTERFACE_COMPORT = new Guid("86E0D1E0-8089-11D0-9CE4-08003E301F73");
            Guid GUID_DEVINTERFACE_MODEM = new Guid("2c7089aa-2e0e-11d1-b114-00c04fc2aae4");
            List<ComInfo> ports = new List<ComInfo>();
            Guid[] ClassGuid = new Guid[4];
            ClassGuid[0] = GUID_DEVCLASS_PORTS;
            ClassGuid[1] = GUID_DEVCLASS_MODEM;
            ClassGuid[2] = GUID_DEVINTERFACE_COMPORT;
            ClassGuid[3] = GUID_DEVINTERFACE_MODEM;
            uint[] dwFlag = new uint[4];
            dwFlag[0] = DIGCF_PRESENT;
            dwFlag[1] = DIGCF_PRESENT;
            dwFlag[2] = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE;
            dwFlag[3] = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE;
            List<string> temp_portname_list = new List<string>();//用于判断是否有重复的
            for (int index = 0; index < ClassGuid.Length; index++)
            {
                IntPtr hDevInfo = (IntPtr)INVALID_HANDLE_VALUE;
                //函数返回一个包含本机上所有被请求的设备信息的设备信息集句柄
                hDevInfo = SetupDiGetClassDevs(ref ClassGuid[index], 0, IntPtr.Zero, dwFlag[index]);
                if (hDevInfo == (IntPtr)INVALID_HANDLE_VALUE)
                {
                    return ports;
                }
                SP_DEVINFO_DATA DeviceInfoData = new SP_DEVINFO_DATA();
                DeviceInfoData.cbSize = (uint)Marshal.SizeOf(new SP_DEVINFO_DATA());
                //在64位系统上cbSize 是32 32位系统上是28，如果不对则会读取出错
                DeviceInfoData.DevInst = 0;
                DeviceInfoData.ClassGuid = Guid.Empty;
                DeviceInfoData.Reserved = IntPtr.Zero;
                //枚举设备信息
                uint i = 0;
                while (SetupDiEnumDeviceInfo(hDevInfo, i++, ref DeviceInfoData))
                {
                    //Get port name
                    const uint DIREG_DEV = 0x00000001;
                    const uint KEY_READ = 0x00020019;
                    //打开枚举成功的设备注册表句柄hkey，并获取该设备对应的相关信息 DeviceInfoData
                    IntPtr hkey = SetupDiOpenDevRegKey(
                        hDevInfo,
                        ref DeviceInfoData,
                        DICS_FLAG_GLOBAL,
                        0,
                        DIREG_DEV,
                        KEY_READ);
                    //根据句柄 和关键词获取 portName
                    string portName = GetStringValue(hkey, "PortName");
                    //关闭注册表句柄
                    RegCloseKey(hkey);
                    if (portName.Length == 0 || portName.ToString().Contains("LPT")) continue;//并口跳过
                    if (temp_portname_list.IndexOf(portName) >= 0) continue;//重复的跳过
                    temp_portname_list.Add(portName);
                    // Get port friendly name 从DeviceInfoData中获取DeviceName信息
                    byte[] DeviceName = new byte[MAX_DEV_LEN];
                    uint PropertyRegDataType = 0;
                    if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, ref DeviceInfoData, SPDRP_FRIENDLYNAME,
                        ref PropertyRegDataType, DeviceName, MAX_DEV_LEN, IntPtr.Zero))
                    {
                        DeviceName = new byte[MAX_DEV_LEN];
                    }
                    // Get DeviceDsc 从DeviceInfoData中获取DeviceDsc信息
                    byte[] DeviceDsc = new byte[MAX_DEV_LEN];
                    if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, ref DeviceInfoData, SPDRP_DEVICEDESC,
                        ref PropertyRegDataType, DeviceDsc, MAX_DEV_LEN, IntPtr.Zero))
                    {
                        DeviceDsc = new byte[MAX_DEV_LEN];
                    }
                    // Get hardware ID 从DeviceInfoData中获取 hardware ID 信息
                    byte[] hardwareID = new byte[MAX_DEV_LEN];
                    if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, ref DeviceInfoData, SPDRP_HARDWAREID,
                        ref PropertyRegDataType, hardwareID, MAX_DEV_LEN, IntPtr.Zero))
                    {
                        hardwareID = new byte[MAX_DEV_LEN];
                    }
                    ComInfo temp_port=new ComInfo();
                    temp_port.PortName = portName.Split('\0').Length > 0 ? portName.Split('\0')[0] : "";
                    var str_arry = Encoding.Unicode.GetString(DeviceDsc).Split('\0');
                    temp_port.Description = str_arry.Length > 0 ? str_arry[0] : "";
                    str_arry = Encoding.Unicode.GetString(hardwareID).Split('\0');
                    temp_port.HardwareId = str_arry.Length > 0 ? str_arry[0] : "";
                    ports.Add(temp_port);
                }
                SetupDiDestroyDeviceInfoList(hDevInfo);//
            }
            return ports.OrderBy(x=>x.PortName).ToList();
        }

        [DllImport("SetupAPI.dll")]
        public static extern bool SetupDiEnumDeviceInfo(
            IntPtr DeviceInfoSet,
            uint MemberIndex,
            ref SP_DEVINFO_DATA DeviceInfoData
        );

        [DllImport("SetupAPI.dll")]
        public static extern IntPtr SetupDiGetClassDevs(
            ref Guid ClassGuid,
            uint Enumerator,
            IntPtr hwndParent,
            uint Flags
        );

        /// <summary>
        /// 根据给定的PID VID 搜寻 对应的串口
        /// </summary>
        /// <param name="pid"></param> PID 注意进制，如 0x5656
        /// <param name="vid"></param> VID 注意进制，如 0x5656
        /// <returns></returns> 返回的是符合 PID 和VID 的串口名称列表
        public static string[] GetCOMFromPIDVID(int pid, int vid)
        {
            List<ComInfo> COM_ports = ListPorts();
            string pid_s = pid.ToString("X4");
            string vid_s = vid.ToString("X4");
            ArrayList list = new ArrayList();
            foreach (var port in COM_ports)
            {
                if (port.HardwareId.Contains(pid_s) && port.HardwareId.Contains(vid_s))
                {
                    list.Add(port.PortName);
                }
            }
            return (string[])list.ToArray(typeof(string));
        }

        /// <summary>
        /// 根据给定的PID VID 搜寻 对应的串口
        /// </summary>
        /// <param name="pid"></param> PID 注意进制，如 0x5656
        /// <param name="vid"></param> VID 注意进制，如 0x5656
        /// <returns></returns> 返回的是符合 PID 和VID 的串口名称列表
        public static List<ComInfo> GetCOMInfoFromPIDVID(int pid, int vid)
        {
            List<ComInfo> COM_ports = ListPorts();
            string pid_s = pid.ToString("X4");
            string vid_s = vid.ToString("X4");
            List<ComInfo> list = new List<ComInfo>();
            foreach (var port in COM_ports)
            {
                if (port.HardwareId.Contains(pid_s) && port.HardwareId.Contains(vid_s))
                {
                    list.Add(port);
                }
            }
            return list;
        }

        [DllImport("ADVAPI32.dll", SetLastError = false)]
        internal static extern int RegCloseKey(IntPtr handle);

        [DllImport("ADVAPI32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
        internal static extern int RegQueryValueEx(IntPtr hKey, string lpValueName,
                    int[] lpReserved, ref int lpType, [Out] byte[] lpData,
                    ref int lpcbData);

        [DllImport("SetupAPI.dll")]
        private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

        [DllImport("setupapi.dll")]
        private static extern bool SetupDiGetDeviceRegistryPropertyW(
            IntPtr DeviceInfoSet,
            ref SP_DEVINFO_DATA DeviceInfoData,
            uint Property,
            ref uint PropertyRegDataType,
            byte[] PropertyBuffer,
            uint PropertyBufferSize,
            IntPtr RequiredSize
            );

        [DllImport("setupapi.dll")]
        private static extern IntPtr SetupDiOpenDevRegKey(
            IntPtr DeviceInfoSet,
            ref SP_DEVINFO_DATA DeviceInfoData,
            uint Scope,
            uint HwProfile,
            uint KeyType,
            uint samDesired
            );

        public class ComInfo
        {
            public string PortName { get; set; }
            public string Description { get; set; }
            public string HardwareId { get; set; }
        }
        // HardwareID (R/W)
        public struct SP_DEVINFO_DATA
        {
            public uint cbSize;
            public Guid ClassGuid;
            public uint DevInst;
            public IntPtr Reserved;
        }
    }
}
